// Impl

// Local
#include "simrenderer.h"
#include "FboInSGRenderer.h"
#include "simphysicsobject.h"

SimRenderer::SimRenderer()
{
    glClearColor(0.1f, 0.1f, 0.2f, 1.0f);

    QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, &mProgram);
    vshader->compileSourceFile(":/vertexShader.vsh");

    QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, &mProgram);
    fshader->compileSourceFile(":/fragmentShader.fsh");

    mProgram.addShader(vshader);
    mProgram.addShader(fshader);
    mProgram.link();

    mVertexAttrLoc = mProgram.attributeLocation("vertex");
    mNormalAttrLoc = mProgram.attributeLocation("normal");
    mColorAttrLoc  = mProgram.attributeLocation("aColor");
    mMatrixUniformLoc = mProgram.uniformLocation("matrix");

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

    createGrid();

    createGeometry();
}

void
SimRenderer::createGrid()
{
	mGridVertices     << QVector3D(0, 0, 0) << QVector3D(2, 0, 0)     // x-axis
				      << QVector3D(0, 0, 0) << QVector3D(0, 2, 0)     // y-axis
				      << QVector3D(0, 0, 0) << QVector3D(0, 0, 2)    // z-axis

                      << QVector3D(2, 0, 0.0) << QVector3D(-2, 0, 0.0)     // x-grid, || to x-axis
	                  << QVector3D(2, 0, 0.5) << QVector3D(-2, 0, 0.5)
	                  << QVector3D(2, 0, 1.0) << QVector3D(-2, 0, 1.0)
	                  << QVector3D(2, 0, 1.5) << QVector3D(-2, 0, 1.5)
	                  << QVector3D(2, 0, 2.0) << QVector3D(-2, 0, 2.0)

	                  << QVector3D(2, 0, -0.5) << QVector3D(-2, 0, -0.5)     // x-grid, || to x-axis
	                  << QVector3D(2, 0, -1.0) << QVector3D(-2, 0, -1.0)
	                  << QVector3D(2, 0, -1.5) << QVector3D(-2, 0, -1.5)
	                  << QVector3D(2, 0, -2.0) << QVector3D(-2, 0, -2.0)

	                  << QVector3D(0.0, 0, 2) << QVector3D(0.0, 0, -2)   // z-grid, || to z-axis
	                  << QVector3D(0.5, 0, 2) << QVector3D(0.5, 0, -2)
	                  << QVector3D(1.0, 0, 2) << QVector3D(1.0, 0, -2)
	                  << QVector3D(1.5, 0, 2) << QVector3D(1.5, 0, -2)
	                  << QVector3D(2.0, 0, 2) << QVector3D(2.0, 0, -2)

	                  << QVector3D(-0.5, 0, 2) << QVector3D(-0.5, 0, -2)   // z-grid, || to z-axis
	                  << QVector3D(-1.0, 0, 2) << QVector3D(-1.0, 0, -2)
	                  << QVector3D(-1.5, 0, 2) << QVector3D(-1.5, 0, -2)
	                  << QVector3D(-2.0, 0, 2) << QVector3D(-2.0, 0, -2);




	    mGridColors << QVector3D(1, 0, 0) << QVector3D(1, 0, 0)
                    << QVector3D(0, 1, 0) << QVector3D(0, 1, 0)
                    << QVector3D(0, 0, 1) << QVector3D(0, 0, 1)

	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)

	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f)
	                << QVector3D(1.0f, 1.0f, 1.0f) << QVector3D(1.0f, 1.0f, 1.0f);


}

void
SimRenderer::createGeometry()
{
	 mVertices << QVector3D(-0.5, -0.5,  0.5) << QVector3D( 0.5, -0.5,  0.5) << QVector3D( 0.5,  0.5,  0.5) // Front
	                << QVector3D( 0.5,  0.5,  0.5) << QVector3D(-0.5,  0.5,  0.5) << QVector3D(-0.5, -0.5,  0.5)
	                << QVector3D( 0.5, -0.5, -0.5) << QVector3D(-0.5, -0.5, -0.5) << QVector3D(-0.5,  0.5, -0.5) // Back
	                << QVector3D(-0.5,  0.5, -0.5) << QVector3D( 0.5,  0.5, -0.5) << QVector3D( 0.5, -0.5, -0.5)
	                << QVector3D(-0.5, -0.5, -0.5) << QVector3D(-0.5, -0.5,  0.5) << QVector3D(-0.5,  0.5,  0.5) // Left
	                << QVector3D(-0.5,  0.5,  0.5) << QVector3D(-0.5,  0.5, -0.5) << QVector3D(-0.5, -0.5, -0.5)
	                << QVector3D( 0.5, -0.5,  0.5) << QVector3D( 0.5, -0.5, -0.5) << QVector3D( 0.5,  0.5, -0.5) // Right
	                << QVector3D( 0.5,  0.5, -0.5) << QVector3D( 0.5,  0.5,  0.5) << QVector3D( 0.5, -0.5,  0.5)
	                << QVector3D(-0.5,  0.5,  0.5) << QVector3D( 0.5,  0.5,  0.5) << QVector3D( 0.5,  0.5, -0.5) // Top
	                << QVector3D( 0.5,  0.5, -0.5) << QVector3D(-0.5,  0.5, -0.5) << QVector3D(-0.5,  0.5,  0.5)
	                << QVector3D(-0.5, -0.5, -0.5) << QVector3D( 0.5, -0.5, -0.5) << QVector3D( 0.5, -0.5,  0.5) // Bottom
	                << QVector3D( 0.5, -0.5,  0.5) << QVector3D(-0.5, -0.5,  0.5) << QVector3D(-0.5, -0.5, -0.5);

	    mColors << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) // Front
	            << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) << QVector3D(1, 0, 0)
	            << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) // Back
	            << QVector3D(1, 0, 0) << QVector3D(1, 0, 0) << QVector3D(1, 0, 0)
	            << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) // Left
	            << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) << QVector3D(0, 1, 0)
	            << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) // Right
	            << QVector3D(0, 1, 0) << QVector3D(0, 1, 0) << QVector3D(0, 1, 0)
	            << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) // Top
	            << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) << QVector3D(0, 0, 1)
	            << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) // Bottom
	            << QVector3D(0, 0, 1) << QVector3D(0, 0, 1) << QVector3D(0, 0, 1);
}

/*
 * This function is called when the FBO should be rendered into.
 * The framebuffer is bound at this point and the glViewport has been
 * set up to match the FBO size.
 * The FBO will be automatically unbound after the function returns.
 */
void
SimRenderer::render() {
    glDepthMask(true);

    glClearColor(0.5f, 0.5f, 0.7f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

    glFrontFace(GL_CW);
    glCullFace(GL_FRONT);
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);


/*  // Earlier...
    QMatrix4x4 modelview;
    modelview.rotate(mAlpha, 0.0f, 1.0f, 0.0f);
    modelview.rotate(mBeta, 1.0f, 0.0f, 0.0f);
//    modelview.rotate(mAlpha, 0.0f, 0.0f, 1.0f);
    modelview.scale(1.0);
 //   modelview.translate(0.0f, 0f, 0.0f);

    mProgram.bind();
    mProgram.setUniformValue(mMatrixUniformLoc, modelview);
*/
    // Now
    QMatrix4x4 mMatrix;
    QMatrix4x4 local, vMatrix;

    QMatrix4x4 cameraTransformation;
    cameraTransformation.rotate(mAlpha, 0, 1, 0); // 25
    cameraTransformation.rotate(mBeta, 1, 0, 0);  // 25

 //   cameraTransformation.translate(0, 0, mDistance);

    QVector3D cameraPosition = cameraTransformation * QVector3D(0, 0, mDistance);
    QVector3D cameraUpDirection = cameraTransformation * QVector3D(0, -1, 0);

    vMatrix.lookAt(cameraPosition, QVector3D(0, 0, 0), cameraUpDirection);

    //vMatrix = local.inverted();

    mProgram.bind();
    mProgram.setUniformValue(mMatrixUniformLoc, mProjMatrix *  vMatrix * mMatrix );
    //mProgram.setUniformValue("color", QColor(Qt::green));

    // -----------------------------------

    // The rendering


    renderPerspectiveGrid();

    renderPhysicsDebugDraw();

    renderPhysicsMeshes();



    //-------------------------------------

    mProgram.release();

    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);

    update();

	// Clear rendered data - this is not the best way to do this
	// We can avoid clearing - which frees memory by keeping track of the number of
	// vertices last inserted and using that as the size instead of clearing and filling
	// the QVector during each render pass
    mPhysics.getDebugDrawer().clearVertices();

    mPhysics.stepPhysics();
}

void
SimRenderer::renderPerspectiveGrid()
{
	// Draw the grid
	// Send vertices to GPU
	mProgram.enableAttributeArray(mVertexAttrLoc);
	mProgram.setAttributeArray(mVertexAttrLoc, mGridVertices.constData());
	// Send colors to GPU
	mProgram.setAttributeArray(mColorAttrLoc, mGridColors.constData());
	mProgram.enableAttributeArray(mColorAttrLoc);
	// Render
	glDrawArrays(GL_LINES, 0, mGridVertices.size());

	mProgram.disableAttributeArray(mVertexAttrLoc);
	mProgram.disableAttributeArray(mColorAttrLoc);
}

void
SimRenderer::renderPhysicsDebugDraw()
{
    QVector<QVector3D>& debugDrawVertices = mPhysics.getDebugDrawer().getVertices();
    QVector<QVector3D>& debugDrawColors = mPhysics.getDebugDrawer().getColors();

	// Draw the grid - we are still using mProgram that was bound in render()
	// Send vertices to GPU
	mProgram.enableAttributeArray(mVertexAttrLoc);
	mProgram.setAttributeArray(mVertexAttrLoc, debugDrawVertices.constData());
	// Send colors to GPU
	mProgram.setAttributeArray(mColorAttrLoc, debugDrawColors.constData());
	mProgram.enableAttributeArray(mColorAttrLoc);
	// Render
	glDrawArrays(GL_LINES, 0, debugDrawVertices.size());

	mProgram.disableAttributeArray(mVertexAttrLoc);
	mProgram.disableAttributeArray(mColorAttrLoc);
}

void
SimRenderer::renderPhysicsMeshes()
{
	QVector<QVector3D>& debugDrawVertices = mPhysics.getDebugDrawer().getTriangleVertices();
	QVector<QVector3D>& debugDrawColors = mPhysics.getDebugDrawer().getTriangleColors();


	// Send vertices to GPU
    mProgram.enableAttributeArray(mVertexAttrLoc);
    mProgram.setAttributeArray(mVertexAttrLoc, debugDrawVertices.constData());
    // Send colors to GPU
    mProgram.setAttributeArray(mColorAttrLoc, debugDrawColors.constData());
    mProgram.enableAttributeArray(mColorAttrLoc);
    // Render
    glDrawArrays(GL_TRIANGLES, 0, 36 /*debugDrawVertices.size()*/);

    mProgram.disableAttributeArray(mVertexAttrLoc);
    	mProgram.disableAttributeArray(mColorAttrLoc);

    	// Clear rendered data - this is not the best way to do this
    	// We can avoid clearing - which frees memory by keeping track of the number of
    	// vertices last inserted and using that as the size instead of clearing and filling
    	// the QVector during each render pass
        mPhysics.getDebugDrawer().clearVertices();
}

void
SimRenderer::renderHUD()
{

}


/*
 * This function is called as a result of QQuickFramebufferObject::update().
 * Use this function to update the renderer with changes that have occurred in the
 * item. item is the item that instantiated this renderer. The function is called
 * once before the FBO is created.
 * For instance, if the item has a color property which is controlled by QML, one
 * should call QQuickFramebufferObject::update() and use synchronize() to copy the
 * new color into the renderer so that it can be used to render the next frame.
 * This function is the only place when it is safe for the renderer and the item to
 * read and write each others members.
 */
void
SimRenderer::synchronize(QQuickFramebufferObject * item)
{
    FboInSGRenderer* fbo = static_cast<FboInSGRenderer*>(item);

    mAlpha = fbo->mAlpha;
    mBeta = fbo->mBeta;
    mDistance = fbo->mDistance;

}
